package com.log4ic.utils.convert.pdf;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfReader;
import com.log4ic.utils.io.FileUtils;
import com.log4ic.utils.security.PDFSecurer;

/**
 * @author: 张立鑫
 * @date: 11-8-15 下午4:21 PDF 转换器
 */
public class PDFConverter {
	private static final Log LOGGER = LogFactory.getLog(PDFConverter.class);

	private static String COMMAND = "";

	private static Integer SINGLE_PAGE_MODE_MAX_THREAD = 5;

	public static void setCommand(String command) {
		PDFConverter.COMMAND = command;
		LOGGER.debug("设置命令配置为 :" + command);
	}

	public static String getCommand() {
		return COMMAND;
	}

	public static void setSinglePageModeMaxThread(Integer value) {
		PDFConverter.SINGLE_PAGE_MODE_MAX_THREAD = value;
		LOGGER.debug("设置单页面转换模式最大线程配置为 :" + value);
	}

	public static Integer getSinglePageModeMaxThread() {
		return SINGLE_PAGE_MODE_MAX_THREAD;
	}

	public static void setOnePageModeMaxThreadConfig(Integer value)
			throws Exception {
		setSinglePageModeMaxThread(value);
	}

	public static void setCommandConfig(String command) throws Exception {
		setCommand(command);
	}

	public int execCommand(String command, final List<String> outResources)
			throws InterruptedException, IOException {
		LOGGER.debug(command);
		final Process convertProcess = Runtime.getRuntime().exec(command);
		final InputStream inputStream = convertProcess.getInputStream();
		final InputStream errorStream = convertProcess.getErrorStream();

		new Thread() {
			public void run() {
				BufferedReader br = new BufferedReader(new InputStreamReader(
						inputStream));
				try {
					String line = null;

					while ((line = br.readLine()) != null) {
						if (outResources != null) {
							outResources.add(line);
						}
						LOGGER.debug(line);
					}
				} catch (IOException e) {
					LOGGER.error(e);
				}
			}
		}.start();
		new Thread() {
			public void run() {
				BufferedReader br = new BufferedReader(new InputStreamReader(
						errorStream));
				try {

					String line = null;
					while ((line = br.readLine()) != null) {
						LOGGER.debug(line);
						if (outResources != null) {
							outResources.add(line);
						}
					}
				} catch (IOException e) {
					LOGGER.error(e);
				}
			}
		}.start();
		convertProcess.waitFor();
		return convertProcess.exitValue();
	}

	private PDFConverterDeploy deploy(File pdfFile, String outPath,
			boolean splitPage, boolean poly2bitmap) throws Exception,
			DocumentException {
		if (!pdfFile.isFile() || !pdfFile.exists()) {
			throw new Exception("Not a Valid PDF File!");
		}

		String pdfPath = pdfFile.getPath();

		String pdfFullName = pdfFile.getName();

		int i = pdfFullName.toLowerCase().lastIndexOf(".pdf");
		if (i == -1) {
			throw new Exception("Not a Valid PDF File!");
		}

		String pdfName = pdfFullName.substring(0, i);

		LOGGER.debug("---加载文档---");

		File outputDirectory = new File(FileUtils.appendFileSeparator(outPath)
				+ pdfName);

		// if is a file , backup the file
		if (outputDirectory.isFile()) {
			outputDirectory.renameTo(new File(outputDirectory.getPath()
					+ ".backup"));
		}

		if (!outputDirectory.exists()) {
			outputDirectory.mkdirs();
		}

		outPath = FileUtils.appendFileSeparator(outputDirectory.getPath());
		File decryptedPdf = new File(FileUtils.getFilePrefix(pdfFile)
				+ "_decrypted.pdf");
		File info = new File(FileUtils.appendFileSeparator(outputDirectory
				.getPath()) + "info");
		int pageCount = 0;
		if (!info.exists()) {
			PdfReader reader = new PdfReader(pdfPath);
			pageCount = reader.getNumberOfPages();

			if (!decryptedPdf.exists()) {
				// if pdf is a encrypted file unencrypted
				if (reader.isEncrypted()) {
					LOGGER.debug("encrypted pdf! 准备另存");
					pdfPath = PDFSecurer
							.decrypt(
									reader,
									FileUtils.getFilePrefix(pdfFile)
											+ "_decrypted.pdf").getPath();
					LOGGER.debug("PDF解密完成");
				} else {
					LOGGER.debug("---文档未加密---");
				}
			} else {
				pdfPath = decryptedPdf.getPath();
			}

			reader.close();

			if (pageCount != 0) {
				OutputStream out = null;
				try {

					info.createNewFile();

					out = new FileOutputStream(info);

					out.write((pageCount + "").getBytes("UTF-8"));
				} finally {
					if (out != null) {
						out.flush();
						out.close();
					}
				}

			}
		} else {
			pdfPath = decryptedPdf.getPath();

			FileInputStream in = new FileInputStream(info);

			BufferedReader reader = new BufferedReader(new InputStreamReader(
					in, "UTF-8"));

			try {
				pageCount = Integer.parseInt(reader.readLine());
			} catch (Exception e) {
			} finally {
				reader.close();
				in.close();
			}
		}

		return new PDFConverterDeploy(outputDirectory, pageCount, COMMAND
				.replace("${in}", pdfPath).replace("${out}",
						outPath + (splitPage ? "page%.swf" : "page.swf"))
				+
				// 是否将图片转换成点阵形式
				(poly2bitmap ? " -s poly2bitmap -s multiply=4" : ""));

	}

	public PDFConvertError checkError(List<String> outResources) {
		boolean errorMsg = false;
		int position = -1;
		String item;
		PDFConvertError error = new PDFConvertError();
		for (int i = outResources.size() - 1; i >= 0; i--) {

			item = outResources.get(i);

			if (!errorMsg) {

				if (item.lastIndexOf(PDFConvertErrorType.COMPLEX
						.getErrorMessage()) > -1) {
					errorMsg = true;
					error.setType(PDFConvertErrorType.COMPLEX);
				} else if (item.lastIndexOf(PDFConvertErrorType.INVALID_CHARID
						.getErrorMessage()) > -1) {
					errorMsg = true;
					error.setType(PDFConvertErrorType.INVALID_CHARID);
				}

			} else if (position == -1) {
				String reg = "NOTICE  processing PDF page ";
				position = item.lastIndexOf(reg);
				if (position != -1) {
					item = item.replace(reg, "");
					error.setPosition(Integer.parseInt(item.split(" ")[0]));
				}
			}
		}
		return error;
	}

	private void errorHandler(String command, final File pdfFile,
			final String outPath, boolean splitPage, boolean poly2bitmap,
			List<String> outResources) throws Exception {
		// 如果第一次没有将图片转换成点阵图报错，则进行第二次转换，并将图片转换成点阵
		final PDFConvertError error = checkError(outResources);
		if (error.getType() == PDFConvertErrorType.COMPLEX && !poly2bitmap) {
			LOGGER.debug("---第一次转换失败，进行第二次转换 poly2bitmap = true ---");
			if (splitPage) {
				new Thread() {
					public void run() {
						try {
							convert(pdfFile, outPath, error.getPosition(),
									error.getPosition(), true, true);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}.start();
				convertAsOnePageMode(pdfFile, outPath, error.getPosition() + 1);
			} else {
				convert(pdfFile, outPath, splitPage, true);
			}
		} else {
			throw new Exception("Conversion failed:" + error.getType() + ";\n"
					+ StringUtils.join(outResources, "\n"));
		}
	}

	public void run(String command, File pdfFile, String outPath,
			boolean splitPage, boolean poly2bitmap) throws Exception {
		LOGGER.debug("---开始---");
		List<String> outResources = new ArrayList<String>();

		if (execCommand(command, outResources) != 0) {
			errorHandler(command, pdfFile, outPath, splitPage, poly2bitmap,
					outResources);
		}
		LOGGER.debug("---结束---");
	}

	public File convertAsOnePageMode(File pdfFile, String outPath)
			throws Exception {
		return convertAsOnePageMode(pdfFile, outPath, 1, null, false);
	}

	public File convertAsOnePageMode(File pdfFile, String outPath, int startPage)
			throws Exception {
		return convertAsOnePageMode(pdfFile, outPath, startPage, null, false);
	}

	public File convertAsOnePageMode(File pdfFile, String outPath,
			boolean poly2bitmap) throws Exception {
		return convertAsOnePageMode(pdfFile, outPath, 1, null, poly2bitmap);
	}

	public File convertAsOnePageMode(final File pdfFile, final String outPath,
			final int startPage, final Integer endPage,
			final boolean poly2bitmap) throws Exception {

		final List<Thread> pool = new LinkedList<Thread>();

		int covertCount = 0;

		PDFConverterDeploy deploy = deploy(pdfFile, outPath, true, poly2bitmap);

		String command = deploy.getCommand();

		int page = getSinglePageModeMaxThreadCount(deploy);

		int pageCount = deploy.getPageCount();

		for (int i = startPage; i <= (endPage == null || endPage > pageCount ? pageCount
				: endPage); i++) {
			final String commandExec = command += " -p " + i;
			Thread t = new Thread() {
				public void run() {
					List<String> outResources = new ArrayList<String>();
					try {
						if (execCommand(commandExec, outResources) != 0) {
							PDFConvertError error = checkError(outResources);
							if (error.getType() == PDFConvertErrorType.COMPLEX
									&& !poly2bitmap) {
								LOGGER.debug("---第一次转换失败，进行第二次转换---");
								execCommand(commandExec + " -s poly2bitmap",
										outResources);
							} else if (error.getType() != PDFConvertErrorType.NONE) {
								throw new Exception("Conversion failed:"
										+ error.getType() + ";\n"
										+ StringUtils.join(outResources, "\n"));
							}
						}
					} catch (Exception e) {
						LOGGER.error(e);
					}

				}
			};
			pool.add(t);
			t.start();
			covertCount++;
			if (covertCount == page) {
				for (Thread thread : pool) {
					thread.join();
				}
				pool.removeAll(pool);
				covertCount = 0;
			}
		}
		return deploy.getOutputDirectory();
	}

	public File convert(File pdfFile, String outPath, int startPage,
			Integer endPage, boolean splitPage, boolean poly2bitmap)
			throws Exception {
		PDFConverterDeploy deploy = deploy(pdfFile, outPath, splitPage,
				poly2bitmap);
		String command = deploy.getCommand();
		int pageCount = deploy.getPageCount();
		command += " -p "
				+ startPage
				+ (endPage != null && startPage == endPage ? "" : "-"
						+ (endPage == null || endPage > pageCount ? pageCount
								: endPage));

		run(command, pdfFile, outPath, splitPage, poly2bitmap);
		return deploy.getOutputDirectory();
	}

	//
	// public File convert(File pdfFile, String outPath) throws Exception {
	// return convert(pdfFile, outPath, true, false);
	// }

	public File convert(File pdfFile, String outPath, boolean poly2bitmap)
			throws Exception {
		return convert(pdfFile, outPath, true, poly2bitmap);
	}

	/**
	 * pdf转换为swf
	 * 
	 * @param pdfFile
	 * @param outPath
	 * @param splitPage
	 * @param poly2bitmap
	 * @return 返回转换后输出文件目录
	 * @throws Exception
	 */
	public File convert(File pdfFile, String outPath, boolean splitPage,
			boolean poly2bitmap) throws Exception {

//		LOGGER.debug("---准备转换文档---");

		PDFConverterDeploy deploy = deploy(pdfFile, outPath, splitPage,
				poly2bitmap);
		String command = deploy.getCommand();
		run(command, pdfFile, outPath, splitPage, poly2bitmap);

		return deploy.getOutputDirectory();
	}

	/**
	 * pdf转换为swf
	 * 
	 * @param pdfFile
	 * @param outPath
	 * @param splitPage
	 * @param poly2bitmap
	 * @return 返回转换后输出文件目录
	 * @throws Exception
	 */
	public String convert(File pdfFile, String outPath) throws Exception {
//		LOGGER.debug("---准备转换文档---");
		String inputFile = pdfFile.getPath();
		String fileName = pdfFile.getName();
		String outputFile = outPath + "/" + fileName + ".swf";
		String command = COMMAND.replace("${in}", inputFile).replace("${out}",
				outputFile);
		try {
//			System.out.println("开始转换文档: " + inputFile);
			boolean success = executeCmdFlash(command);
			if(success)
				System.out.println("成功转换文件！");
			else
				System.out.println("转换文档失败！");
			return fileName + ".swf";
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("转换文档失败！");
			return null;
		}
	}

	public int getSinglePageModeMaxThreadCount(PDFConverterDeploy deploy) {
		int pageCount = deploy.getPageCount();
		if (SINGLE_PAGE_MODE_MAX_THREAD < 1) {
			return pageCount;
		} else {
			return SINGLE_PAGE_MODE_MAX_THREAD > pageCount ? pageCount
					: SINGLE_PAGE_MODE_MAX_THREAD;
		}
	}

	public synchronized boolean executeCmdFlash(String cmd) {
		StringBuffer stdout = new StringBuffer();
		try {
			final Process process = Runtime.getRuntime().exec(cmd);
			Runtime.getRuntime().addShutdownHook(new Thread() {
				public void run() {
					process.destroy();
				}
			});
			InputStreamReader inputstreamreader = new InputStreamReader(
					process.getInputStream());
			char c = (char) inputstreamreader.read();
			if (c != '\uFFFF')
				stdout.append(c);
			while (c != '\uFFFF') {
				if (!inputstreamreader.ready()) {
					try {
						process.exitValue();
						break;
					} catch (IllegalThreadStateException _ex) {
						try {
							Thread.sleep(100L);
						} catch (InterruptedException _ex2) {
						}
					}
				} else {
					c = (char) inputstreamreader.read();
					stdout.append(c);
				}
			}
			try {
				inputstreamreader.close();
			} catch (IOException ioexception2) {
				System.err.println("RunCmd : Error closing InputStream "
						+ ioexception2);
				return false;
			}
		} catch (Throwable e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
}
